//
// System.Security.Cryptography HashAlgorithm Class implementation
//
// Authors:
//   Matthew S. Ford (Matthew.S.Ford@Rose-Hulman.Edu)
//   Sebastien Pouliot (spouliot@motus.com)
//
// Copyright 2001 by Matthew S. Ford.
// Portions (C) 2002 Motus Technologies Inc. (http://www.motus.com)
//
// Comment: Adapted to the Project from Mono CVS as Sebastien Pouliot suggested to enable
// support of Npgsql MD5 authentication in platforms which don't have support for MD5 algorithm.
//


using System;
using System.IO;

namespace Npgsql
{
	// Comment: Removed the ICryptoTransform implementation as this interface may be not supported by
	// all platforms.

	internal abstract class HashAlgorithm : IDisposable
	{
		protected byte[] HashValue; // Caches the hash after it is calculated.  Accessed through the Hash property.
		protected int HashSizeValue; // The size of the hash in bits.
		protected int State; // nonzero when in use;  zero when not in use
		private bool disposed;

		/// <summary>
		/// Called from constructor of derived class.
		/// </summary>
		protected HashAlgorithm()
		{
			disposed = false;
		}

		/// <summary>
		/// Finalizer for HashAlgorithm
		/// </summary>
		~HashAlgorithm()
		{
			Dispose(false);
		}

		/// <summary>
		/// Get whether or not the hash can transform multiple blocks at a time.
		/// Note: MUST be overriden if descendant can transform multiple block
		/// on a single call!
		/// </summary>
		public virtual bool CanTransformMultipleBlocks
		{
			get { return true; }
		}

		public virtual bool CanReuseTransform
		{
			get { return true; }
		}

		public void Clear()
		{
			// same as System.IDisposable.Dispose() which is documented
			Dispose(true);
		}

		/// <summary>
		/// Computes the entire hash of all the bytes in the byte array.
		/// </summary>
		public byte[] ComputeHash(byte[] input)
		{
			return ComputeHash(input, 0, input.Length);
		}

		public byte[] ComputeHash(byte[] buffer, int offset, int count)
		{
			if (disposed)
			{
				throw new ObjectDisposedException("HashAlgorithm");
			}

			HashCore(buffer, offset, count);
			HashValue = HashFinal();
			Initialize();

			return HashValue;
		}

		public byte[] ComputeHash(Stream inputStream)
		{
			// don't read stream unless object is ready to use
			if (disposed)
			{
				throw new ObjectDisposedException("HashAlgorithm");
			}

			int l = (int) (inputStream.Length - inputStream.Position);
			byte[] buffer = new byte[l];
			inputStream.Read(buffer, 0, l);

			return ComputeHash(buffer, 0, l);
		}

		// Commented out because it uses the CryptoConfig which can't be available in all platforms

		/*
        /// <summary>
        /// Creates the default implementation of the default hash algorithm (SHA1).
        /// </summary>
        public static HashAlgorithm Create ()
        {
        	return Create ("System.Security.Cryptography.HashAlgorithm");
        }*/

		/*
        /// <summary>
        /// Creates a specific implementation of the general hash idea.
        /// </summary>
        /// <param name="hashName">Specifies which derived class to create.</param>
        public static HashAlgorithm Create (string hashName)
        {
        	return (HashAlgorithm) CryptoConfig.CreateFromName (hashName);
        }*/


		// Changed Exception type because it uses the CryptographicUnexpectedOperationException
		// which can't be available in all platforms.
		/// <summary>
		/// Gets the previously computed hash.
		/// </summary>
		public virtual byte[] Hash
		{
			get
			{
				if (HashValue == null)
				{
					throw new NullReferenceException("HashValue is null");
				}
				return HashValue;
			}
		}

		/// <summary>
		/// When overridden in a derived class, drives the hashing function.
		/// </summary>
		/// <param name="rgb"></param>
		/// <param name="start"></param>
		/// <param name="size"></param>
		protected abstract void HashCore(byte[] rgb, int start, int size);

		/// <summary>
		/// When overridden in a derived class, this pads and hashes whatever data might be left in the buffers and then returns the hash created.
		/// </summary>
		protected abstract byte[] HashFinal();

		/// <summary>
		/// Returns the size in bits of the hash.
		/// </summary>
		public virtual int HashSize
		{
			get { return HashSizeValue; }
		}

		/// <summary>
		/// When overridden in a derived class, initializes the object to prepare for hashing.
		/// </summary>
		public abstract void Initialize();

		protected virtual void Dispose(bool disposing)
		{
			disposed = true;
		}

		/// <summary>
		/// Must be overriden if not 1
		/// </summary>
		public virtual int InputBlockSize
		{
			get { return 1; }
		}

		/// <summary>
		/// Must be overriden if not 1
		/// </summary>
		public virtual int OutputBlockSize
		{
			get { return 1; }
		}

		void IDisposable.Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this); // Finalization is now unnecessary
		}

		/// <summary>
		/// Used for stream chaining.  Computes hash as data passes through it.
		/// </summary>
		/// <param name="inputBuffer">The buffer from which to grab the data to be copied.</param>
		/// <param name="inputOffset">The offset into the input buffer to start reading at.</param>
		/// <param name="inputCount">The number of bytes to be copied.</param>
		/// <param name="outputBuffer">The buffer to write the copied data to.</param>
		/// <param name="outputOffset">At what point in the outputBuffer to write the data at.</param>
		public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
		{
			Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
			HashCore(inputBuffer, inputOffset, inputCount);

			return inputCount;
		}

		/// <summary>
		/// Used for stream chaining.  Computes hash as data passes through it.  Finishes off the hash.
		/// </summary>
		/// <param name="inputBuffer">The buffer from which to grab the data to be copied.</param>
		/// <param name="inputOffset">The offset into the input buffer to start reading at.</param>
		/// <param name="inputCount">The number of bytes to be copied.</param>
		public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
		{
			byte[] outputBuffer = new byte[inputCount];

			Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, 0, inputCount);

			HashCore(inputBuffer, inputOffset, inputCount);
			HashValue = HashFinal();
			Initialize();

			return outputBuffer;
		}
	}
}